/**
 *       Copyright (c) 2012 Lukas Zaruba
 *
 *   This file is part of Robot Playground.
 *
 *   Robot Playground is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Robot Playground is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with Robot Playground. If not, see <http://www.gnu.org/licenses/>.
 */
package net.zarubsys.commons.scripting.internal;

import java.io.IOException;
import java.io.Reader;

import net.zarubsys.commons.scripting.ScriptLocator;
import net.zarubsys.commons.scripting.ScriptingException;

import org.apache.log4j.Logger;

/**
 * Based on scripting language from Media Solutions Slovakia, a.s.
 * @author Lukas Zaruba, lukas.zaruba@gmail.com
 */
public class TokenReader {
	
	private static final Logger log = Logger.getLogger(TokenReader.class);

	private EOLParser eolParser = new EOLParser();

	Reader reader;

    /** associated reader's file name */
    private final String fileName;

	/** one char buffer */
	int character = -1;

	/** line number */
	int lineNumber = 1;

	/** char in line number (starting at 1, but initialised to 0!) */
	int characterNumber = 1;

	/** total char count */
	int total_char_count = 0;

	/** constructor */
	public TokenReader(Reader r, String fileName) {
		super();
		reader = r;
        this.fileName = fileName;
	}

	/** returns line number (starting at 1) */
	public int getLineNumber() {
		return lineNumber;
	}

	/** set line number */
	public void setLineNumber(int line_no) {
		this.lineNumber = line_no;
	}

	/** returns char in line number (starting at 1) */
	public int getCharNumber() {
		return characterNumber;
	}

	/** returns total count of characters */
	public int getTotalCharCount() {
		return total_char_count;
	}

	/** update line etc. numbers */
	int updateCounts(int ic) {
		if (ic == -1)
			return ic;
		char c = (char) ic;
		total_char_count++;
		characterNumber++;
		
		// eol detection
		if (eolParser.isEOL(c)) {
			lineNumber++;
			characterNumber = 1;
		}
		return ic;
	}

	public int read() throws IOException {
		if (character == -1) {
			character = updateCounts(reader.read());
		}
		int ic = character;
		character = updateCounts(reader.read());
		return ic;
	}

	/** read ahead one char */
	public int readAhead() throws IOException {
		if (character == -1) {
			character = updateCounts(reader.read());
		}
		return character;
	}

	/** read characters into portion of an array */
	public int read(char[] cbuf, int off, int len) throws IOException {
			int ret = 0;
			for (int i = off; i < off + len; i++) {
				int ic = read();
				if (ic == -1)
					break;
				cbuf[i] = (char) ic;
				ret++;
			}
			return ret;
	}

	/** skip characters */
	public long skip(long n) throws IllegalArgumentException, IOException {
		if (n < 0)
			throw new IllegalArgumentException(n + " is negative");
			long ret = 0;
			while (n > 0) {
				if (read() == -1)
					break;
				ret++;
				n--;
			}
			return ret;
	}

	/** is this stream ready to be read ? */
	public boolean ready() throws IOException {
			if (character == -1) {
				return reader.ready();
			}
			return true;
	}

	public void close() throws IOException {
		reader.close();
	}

	/** skip whitespaces */
	public void skipWhitespaces() throws IOException {
			int ic;
			while ((ic = readAhead()) != -1) {
				if (!Character.isWhitespace((char) ic))
					break;
				read();
		}
	}

	/** skip line */
	public void skipLine() throws IOException {
			int ic;
			while ((ic = read()) != -1) {
				char c = (char) ic;

				// eol detection
				if (eolParser.isEOL(c)) {
					return;
				}
			}
	}

	/** read identifier */
	public String readIdentifier() throws IOException, ScriptingException {
			StringBuffer str_buf = new StringBuffer();
			skipWhitespaces();
			int ic = readAhead();
			if (ic == -1)
				throw new ScriptingException("Preliminary end of input", getCurrentLocation());
			if (Character.isLetter((char) ic) || (ic == '_')) {
				str_buf.append((char) ic);
				read();
				while ((ic = readAhead()) != -1) {
					if (Character.isLetterOrDigit((char) character) || (character == '_') || (character == '.')) {
						str_buf.append((char) character);
						read();
					} else
						break;
				}
			}
			if (log.isDebugEnabled()) {
				log.debug("TokenReader.readIdentifier: read identifier '" + str_buf.toString() + "'");
			}
			return str_buf.toString();
	}

	/** read string */
	public String readString() throws IOException, ScriptingException {
			StringBuffer str_buf = new StringBuffer();
			skipWhitespaces();
			int ic = readAhead();
			if (ic == -1)
				throw new ScriptingException("Preliminary end of input", getCurrentLocation());
			char quote_char = (char) ic;
			read();
			while ((ic = readAhead()) != -1) {
				if (ic == '\\') {
					read();
					ic = readAhead();
					if (ic == -1) {
						throw new ScriptingException("Unterminated '\' escape sequence", getCurrentLocation());
					} else if (ic == '0') {
						str_buf.append('\0');
					} else if (ic == 'n') {
						str_buf.append('\n');
					} else if (ic == 't') {
						str_buf.append('\t');
					} else if (ic == 'r') {
						str_buf.append('\r');
					} else if (ic == 'b') {
						str_buf.append('\b');
					} else {
						str_buf.append((char) ic);
					}
					read();
				} else if (ic == quote_char) {
					break;
				} else {
					str_buf.append((char) ic);
					read();
				}
			}
			if (ic != quote_char)
				throw new ScriptingException("Missing '" + quote_char + "'", getCurrentLocation());
			read();
			if (log.isDebugEnabled()) {
				log.debug("TokenReader.readString: read string '" + str_buf.toString() + "'");
			}
			return str_buf.toString();
	}

	/** read number (no strict testing) */
	public String readNumber() throws IOException, ScriptingException {
			StringBuffer str_buf = new StringBuffer();
			skipWhitespaces();
			int ic = readAhead();
			if (ic == -1)
				throw new ScriptingException("Preliminary end of input", getCurrentLocation());
			while ((ic = readAhead()) != -1) {
				if (Character.isLetterOrDigit((char) ic) || (ic == '.') || (ic == '-')) {
					str_buf.append((char) ic);
					read();
				} else
					break;
			}
			if (log.isDebugEnabled()) {
				log.debug("TokenReader.readNumber: read number '" + str_buf.toString() + "'");
			}
			return str_buf.toString();
	}

    public String getFileName() {
        return fileName;
    }
    
    public ScriptLocator getCurrentLocation() {
        return new ScriptLocator(getFileName(), getLineNumber(), getCharNumber());
    }

}
